// 对 photon 实现 Engine trait
use super::{Engine, SpecTransform};
use crate::pb::*;
use anyhow::{Context, Ok, Result};
use bytes::Bytes;
use image::{DynamicImage, ImageBuffer, ImageFormat};
use lazy_static::lazy_static;
use photon_rs::{
    PhotonImage, effects, filters, multiple, native::open_image_from_bytes, transform,
};
use std::{convert::TryFrom, io::Cursor};

lazy_static! {
    // 预先把水印文件加载为静态变量
    static ref WATERMARK: PhotonImage = {
        // 在编译的时候 include_bytes! 宏会直接把文件读入编译后的二进制
        let data = include_bytes!("../../images/watermark/pyst_rusthon.jpg");
        let watermark = open_image_from_bytes(data)
                    // expect 和 unwrap 的区别就：前者可以附带自定义消息用于快速定位错误，两者遇到Err和None时都会panic
                    .expect("Failed to load watermark image from The specified path.");
        transform::resize(&watermark, 64, 64, transform::SamplingFilter::Nearest)
    };
}

// 我们目前支持 Photon engine
pub struct Photon(PhotonImage);

// 从 Bytes 转换成 Photon 结构
impl TryFrom<Bytes> for Photon {
    type Error = anyhow::Error;

    fn try_from(data: Bytes) -> Result<Self, Self::Error> {
        Ok(Self(
            open_image_from_bytes(&data).context("Buffer should be valid")?,
        ))
    }
}

impl Engine for Photon {
    fn apply(&mut self, specs: &[Spec]) {
        for spec in specs.iter() {
            match spec.data {
                Some(spec::Data::Crop(ref v)) => self.transform(v),
                Some(spec::Data::Contrast(ref v)) => self.transform(v),
                Some(spec::Data::Filter(ref v)) => self.transform(v),
                Some(spec::Data::Fliph(ref v)) => self.transform(v),
                Some(spec::Data::Flipv(ref v)) => self.transform(v),
                Some(spec::Data::Resize(ref v)) => self.transform(v),
                Some(spec::Data::Watermark(ref v)) => self.transform(v),
                // 对于目前不认识的 spec，不做任何处理
                _ => {}
            }
        }
    }

    fn generate(self, format: ImageFormat) -> Vec<u8> {
        match format {
            _ => image_to_buf(self.0, format),
        }
    }
}

impl SpecTransform<&Crop> for Photon {
    fn transform(&mut self, op: &Crop) {
        let img = transform::crop(&mut self.0, op.x1, op.y1, op.x2, op.y2);
        self.0 = img;
    }
}

impl SpecTransform<&Contrast> for Photon {
    fn transform(&mut self, op: &Contrast) {
        effects::adjust_contrast(&mut self.0, op.contrast);
    }
}

impl SpecTransform<&Flipv> for Photon {
    fn transform(&mut self, _op: &Flipv) {
        transform::flipv(&mut self.0);
    }
}

impl SpecTransform<&Fliph> for Photon {
    fn transform(&mut self, _op: &Fliph) {
        transform::fliph(&mut self.0);
    }
}

impl SpecTransform<&Filter> for Photon {
    fn transform(&mut self, op: &Filter) {
        // ⚠️注意尽量避免使用已弃用的from_i32枚举转换方法
        // match filter::Filter::from_i32(op.filter) {
        //     Some(filter::Filter::Unspecified) => {}
        //     Some(f) => filters::filter(&mut self.0, f.to_str().unwrap()),
        //     _ => {}
        // }
        let filter = filter::Filter::try_from(op.filter)
            .expect("Convert to [thumbor_rs::pb::abi::filter::Filter] failed");
        if filter != filter::Filter::Unspecified {
            filters::filter(
                &mut self.0,
                filter.to_str().expect(
                    "Convert to str failed for filter(thumbor_rs::pb::abi::filter::Filter)",
                ),
            );
        }
    }
}

impl SpecTransform<&Resize> for Photon {
    fn transform(&mut self, op: &Resize) {
        // from_i32 出现弃用警告，改用 try_from(i32_val).unwrap() 即可。
        let img = match resize::ResizeType::try_from(op.rtype).unwrap() {
            resize::ResizeType::Normal => transform::resize(
                &mut self.0,
                op.width,
                op.height,
                resize::SampleFilter::try_from(op.filter).unwrap().into(),
            ),
            resize::ResizeType::SeamCarve => {
                transform::seam_carve(&mut self.0, op.width, op.height)
            }
        };
        self.0 = img;
    }
}

impl SpecTransform<&Watermark> for Photon {
    fn transform(&mut self, op: &Watermark) {
        // ⚠️注意形参x、y是i64类型，需要进行转换
        multiple::watermark(&mut self.0, &WATERMARK, op.x.into(), op.y.into());
    }
}

// photon 库竟然没有提供在内存中对图片转换格式的方法，只好手工实现
fn image_to_buf(img: PhotonImage, format: ImageFormat) -> Vec<u8> {
    let raw_pixels = img.get_raw_pixels();
    let width = img.get_width();
    let height = img.get_height();
    let img_buffer = ImageBuffer::from_vec(width, height, raw_pixels).unwrap();
    // ⚠️注意：图片格式是否支持Alpha通道
    //  - - - - - - - - - - - - - - - - - - - -
    // ｜格式	    支持色彩空间	是否需要特殊处理｜
    // ｜JPEG	    RGB	            必须转换    ｜
    // ｜PNG	    RGBA/RGB	    自动处理    ｜
    // ｜WebP	    RGBA/RGB	    自动处理    ｜
    // ｜BMP	    RGB	            建议转换    ｜
    //  - - - - - - - - - - - - - - - - - - - -
    // 修改方式一：通过 to_rgb8 方法显示去除Alpha通道 ✅
    // 修改方式二：使用其他支持Alpha通道的格式，比如 PNG、WebP等
    let dynimage = DynamicImage::ImageRgba8(img_buffer).to_rgb8();
    // 从image库0.25+要求输出缓冲区实现Seek trait，而Vec<u8>未实现该特性，故使用Cursor对其包装
    let mut buffer = Cursor::new(Vec::with_capacity(32768));
    dynimage
        .write_to(&mut buffer, format)
        .expect("Failed to encode image");
    buffer.into_inner() // 取出内部Vec<u8>
}

#[cfg(test)]
mod tests {
    use image::AnimationDecoder;
    use image::codecs::gif::{GifDecoder, GifEncoder};
    use std::fs::File;
    use std::io::BufReader;

    #[test]
    fn test_process_gif() {
        // Correct path relative to project root
        let file_in =
            BufReader::new(File::open("images/gif/futu.gif").expect("Open gif file failed"));

        let decoder = GifDecoder::new(file_in).expect("Error creating gif decoder");
        let frames = decoder.into_frames();
        let frames = frames.collect_frames().expect("Error decoding gif");

        // Use File::create for writing output
        let file_out = File::create("images/futu-out.gif").expect("Create output file failed");
        let mut encoder = GifEncoder::new(file_out);

        encoder
            .encode_frames(frames.into_iter())
            .expect("Error encoding gif");
    }
}
